﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace CountJobTags {
    /// <summary>
    /// Encapsulates the process of grabbing the payload from the html
    /// </summary>
    public class PageParser {
        /// <summary>List of US states</summary>
        public string[] STATES = { "AK", "AL", "AR", "AZ", "CA", "CO", "CT", "DC", "DE", "FL", 
                                    "GA", "HI", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MA",
                                    "MD", "ME", "MI", "MN", "MO", "MS", "MT", "NC", "ND", "NE",
                                    "NH", "NJ", "NM", "NV", "NY", "OH", "OK", "OR", "PA", "RI",
                                    "SC", "SD", "TN", "TX", "UT", "VA", "VT", "WA", "WI", "WV", "WY" };
        string _RelocationPattern;
        /// <summary>
        /// The regex which identifies the markup indicating relocation status
        /// </summary>
        public string RelocationPattern {
            get { return _RelocationPattern; }
            set {
                _RelocationPattern = value;
                RelocationRegex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
            }
        }
        private Regex RelocationRegex { get; set; }

        private string _PostedPattern;
        /// <summary>
        /// Used to identify the time the job was posted
        /// </summary>
        public string PostedPattern {
            get{return _PostedPattern;}
            set { 
                _PostedPattern = value;
                PostedRegex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline); 
            }
        }
        private Regex PostedRegex { get; set; }

        private string _EmployerAndLocationPattern;
        /// <summary>
        /// Identifies the employer and location
        /// </summary>
        public string EmployerAndLocationPattern {
            get { return _EmployerAndLocationPattern; }
            set {
                _EmployerAndLocationPattern = value;
                EmployerAndLocationRegex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline); 
            }
        }
        private Regex EmployerAndLocationRegex { get; set; }

        private string _TagsPattern;
        /// <summary>
        /// The tag is a job skill, such as 'Java' or 'PHP' or 'C#'
        /// </summary>
        public string TagsPattern {
            get { return _TagsPattern; }
            set {
                _TagsPattern = value;
                TagsRegex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline);
            }
        }
        private Regex TagsRegex { get; set; }

        private string _WorkRemotePattern;
        /// <summary>
        /// Identifies the html indicating whether successful candidates can work from home
        /// </summary>
        public string WorkRemotePattern {
            get { return _WorkRemotePattern; }
            set { 
                _WorkRemotePattern = value;
                WorkRemoteRegex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline); 
            } 
        }
        private Regex WorkRemoteRegex { get; set; }

        private string _JobIdPattern ;
        /// <summary>
        /// StackOverflow has an ID for each job; the regex pattern finds it
        /// </summary>
        public string JobIdPattern { get{return _JobIdPattern ;}
            set { 
                _JobIdPattern  = value;
                JobIdRegex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline); 
            }
        }
        private Regex JobIdRegex { get; set; }

        /// <summary>
        /// Identifies the current page number, such as '5 of 98'
        /// </summary>
        private string _PagePattern ;
        public string PagePattern {
            get { return _PagePattern; }
            set { 
                _PagePattern  =value;
                PageRegex = new Regex(value, RegexOptions.Compiled | RegexOptions.IgnorePatternWhitespace | RegexOptions.Multiline); 
            }
        }
        private Regex PageRegex { get; set; }

        /// <summary>
        /// Returns a list of the jobs in the text provided
        /// </summary>
        /// <param name="allText">HTML following the pattern currently in use at StackOverflow</param>
        /// <returns>List of JobDesc items</returns>
        /// <remarks>
        /// Each job may have multiple tags. Since the purpose of this app is to analyze the tags, we
        /// return one entry for each tag, cloning the job.
        /// </remarks>
        public List<CountJobTags.JobDesc> ParseText(string allText) {
            List<CountJobTags.JobDesc> allJobs = new List<CountJobTags.JobDesc>();
            int p = allText.IndexOf("<div data-jobid=");
            string divContents = "";
            string jobId = "";
            int curPage = 0, totalPages = 0;
            do {
                if (p >= 0) {
                    jobId = JobIdRegex.Match(allText, p).Value;
                    Match matchPageInfo = PageRegex.Match(allText);
                    if (matchPageInfo.Success) {
                        curPage = int.Parse(matchPageInfo.Groups["CurPage"].Value);
                        totalPages = int.Parse(matchPageInfo.Groups["TotalPages"].Value);
                    }
                }
                divContents = FindMatchingCloseTag(ref p, allText);
                if (!string.IsNullOrWhiteSpace(divContents)) {
                    CountJobTags.JobDesc aJob = new CountJobTags.JobDesc { ID = jobId, PageNumber = curPage, PageTotal = totalPages };
                    Match m = RelocationRegex.Match(divContents);
                    aJob.AllowsRemote = m.Success && m.Groups[1].Value == "Offers Relocation";
                    m = PostedRegex.Match(divContents);
                    if (m.Success)
                        aJob.AgeString = m.Groups[1].Value;
                    m = EmployerAndLocationRegex.Match(divContents);
                    if (m.Success) {
                        if (!string.IsNullOrWhiteSpace(m.Groups["Location"].Value)) {
                            string[] tokens = m.Groups["Location"].Value.Split(',');
                            if (tokens.Length > 1) {
                                aJob.City = tokens[0].Trim();
                                string token2 = tokens[1].Trim();
                                if (tokens.Length == 2 && Array.BinarySearch(STATES, token2) >= 0) {
                                    aJob.Country = "USA";
                                    aJob.Province = token2;
                                } else if (tokens.Length > 2) {
                                    aJob.Province = token2;
                                    aJob.Country = tokens[2];
                                } else if (tokens.Length == 2) {
                                    aJob.Province = token2;
                                }
                            }
                        }
                    }
                    m = WorkRemoteRegex.Match(divContents);
                    if (m.Success && m.Groups[1].Value == "Work Remotely")
                        aJob.AllowsRemote = true;
                    m = TagsRegex.Match(divContents);
                    if (m.Success) {
                        aJob.Tag = string.IsNullOrWhiteSpace(m.Groups[1].Value) ? "Not Specified" : m.Groups[1].Value;
                        allJobs.Add(aJob);
                        m = m.NextMatch();
                        while (m.Success) {
                            aJob = (CountJobTags.JobDesc)aJob.Clone();
                            aJob.Tag = m.Groups[1].Value;
                            allJobs.Add(aJob);
                            m = m.NextMatch();
                        }
                    } else {
                        aJob.Tag = "Not Specified";
                        allJobs.Add(aJob);
                    }
                }
            } while (p >= 0 && !string.IsNullOrWhiteSpace(divContents));
            return allJobs;
        }

        /// <summary>
        /// Finds the endpoint of the current div.
        /// </summary>
        /// <param name="startPos">start position for the current div</param>
        /// <param name="allText">Text to search</param>
        /// <returns>Substring of the text inside the div and its matching close tag</returns>
        private string FindMatchingCloseTag(ref int startPos, string allText) {
            string result = "";
            int openCount = 0;
            int curPos = startPos + 16;
            while (curPos < allText.Length) {
                int nextPos = allText.IndexOf("div", curPos);
                if (nextPos < 0) {
                    //will never happen if the HTML is well formed, but defensive coding is best!
                    result = allText.Substring(startPos);
                    startPos = -1;
                    break;
                } else {
                    if (allText[nextPos - 1] == '<') {
                        openCount++;
                        curPos = nextPos + 3;
                    } else {
                        int i = nextPos - 1;
                        while (i > curPos && string.IsNullOrWhiteSpace(allText.Substring(i, 1)))
                            i--;
                        if (i > curPos && allText.Substring(i - 1, 2) == "</") {
                            if (--openCount == 0) {
                                result = allText.Substring(startPos, nextPos - startPos - 2);
                                startPos = allText.IndexOf("<div data-jobid=", nextPos + 4);
                                break;
                            } else {
                                curPos = nextPos + 3;
                            }
                        } else {
                            curPos = nextPos + 4;
                        }
                    }
                }
            }
            return result;
        }
    }
}
